package game;

import static org.lwjgl.opengl.GL11.*;

import java.io.Serializable;

import org.newdawn.slick.Color;

import data.DataManager;
import application.Configuration;
import application.Window;
import application.messages.Message;
import application.messages.commands.ConsoleCommand;
import application.messages.commands.GameCommand;
import application.messages.commands.parameters.Coordinates2D;
import helpers.Text;
import interfaces.IDrawable;
import game.entities.enemies.EnemiesManager;
import game.entities.enemies.GeneralEnemy;
import game.entities.players.Player;
import game.graphics.effects.GraphicEffectsManager;
import game.graphics.effects.GunshotExplosionEffect;
import game.map.Map;

/**
 * Implements game world (map, player, entities).
 * 
 * @author Tomas Zima
 *
 */
public class World implements IDrawable, Serializable
{
	/**
	 * Constructor.
	 */
	public World()
	{
		collisionChecker.addCollidableObject(player);
		
		GeneralEnemy generalEnemy = new GeneralEnemy();
		generalEnemy.setXY(25 * 32, 1010 * 32);
		entityManager.addEnemy(generalEnemy);
	}
	
	/**
	 * Redraw whole world.
	 */
	public void redraw()
	{
		collisionChecker.checkCollisions();
		
		redrawMapWithLighting(Hours.values()[lastHour % Hours.values().length]);
		player.redraw(cameraX, cameraY);
		entityManager.redraw(cameraX, cameraY);
		gunshotManager.redraw(cameraX, cameraY);
		graphicEffectsManager.redraw(cameraX, cameraY);
		
		// debug: print coordinates of camera
		String coord = "[" + cameraX  / 32 + "; " + cameraY / 32 + "]";; 
		Text t = new Text(850, 20, coord, new Color(1.0f, 1.0f, 1.0f));
		t.redraw();
	}
	
	/**
	 * Redraw map with defined level of light (based on current time).
	 * 
	 * @param hour
	 */
	private void redrawMapWithLighting(Hours hour)
	{
		setDarknessColor(hour);
		
		Window.enableTexColorMixing();
		map.redraw(cameraX, cameraY);
		Window.disableTexColorMixing();
	}
	
	/**
	 * Set background color (R = G = B) which will be mixed into the texture.
	 * It results in dark or light scene.
	 * 
	 * @param hour
	 */
	private void setDarknessColor(Hours hour)
	{
		switch (hour)
		{
			case T_08:
				glColor3f(0.7f, 0.7f, 0.7f);
				break;
				
			case T_12:
				glColor3f(1.0f, 1.0f, 1.0f);
				break;
				
			case T_16:
				glColor3f(0.8f, 0.8f, 0.8f);
				break;
				
			case T_20:
				glColor3f(0.5f, 0.5f, 0.5f);
				break;
				
			case T_24:
				glColor3f(0.2f, 0.2f, 0.2f);
				break;

			case T_04:
				glColor3f(0.5f, 0.5f, 0.5f);
				break;
				
			default:
				glColor3f(0.2f, 0.2f, 0.2f);
				break;
		}
	}
	
	/**
	 * Updates time in the game.
	 * It checks if game is paused or running. Based on this,
	 * time is updated or frozen.
	 * 
	 * @param paused
	 * 	If true, game is paused and time is frozen. If false,
	 * 	time normally runs.
	 */
	public void updateTime(boolean paused)
	{
		/* Originally, lastHourUpdate was initialized at the same time as it was defined.
		 * However, there can be a long time before creation of the class and actually
		 * start of drawing. For this reason, it's necessary to do it like this, because
		 * in other case, time measurement is absolutely wrong.
		 */
		if (lastHourUpdate == -1) lastHourUpdate = System.currentTimeMillis();
		
		// Do NOT run game time when it is frozen
		if (!paused)
		{
			if (lastHourUpdate + (DAY_LENGTH / 6) < System.currentTimeMillis())
			{
				lastHour++;
				lastHourUpdate = System.currentTimeMillis();
			}
		}
		else
		{
			// If game is frozen, lastHourUpdate must be actualized to keep it working right.
			lastHourUpdate = System.currentTimeMillis() + (System.currentTimeMillis() - lastHourUpdate);
		}
	}

	public void testLeftMouseClick(int x, int y)
	{
		int blockX = (cameraX - Configuration.WINDOW_WIDTH / 2 + x) / 32;
		int blockY = (cameraY + Configuration.WINDOW_HEIGHT / 2 - y) / 32;
		
		map.destroyBlock(blockX, blockY);
	}
	
	public void testRightMouseClick(int x, int y, DataManager.BlockType type)
	{
		int blockX = (cameraX - Configuration.WINDOW_WIDTH / 2 + x) / 32;
		int blockY = (cameraY + Configuration.WINDOW_HEIGHT / 2 - y) / 32;
		
		map.addNewBlock(blockX, blockY, type);
	}
	
	public boolean isPlayerInCollision()
	{
		return player.isInCollision();
	}
	
	public void enterNewMessage(Message message)
	{
		switch ((GameCommand) message.command)
		{
			case PLAYER_MOVE_LEFT:
				this.player.moveLeft();
				break;
				
			case PLAYER_MOVE_RIGHT:
				this.player.moveRight();
				break;
				
			case PLAYER_JUMP:
				this.player.jump();
				break;
				
			case PLAYER_STOP_MOVE_LEFT:
				this.player.stopMoveLeft();
				break;
				
			case PLAYER_STOP_MOVE_RIGHT:
				this.player.stopMoveRight();
				break;

			case SHOOT_LEFT:
				this.player.startShooting();
				this.gunshotManager.addGunshot(new Gunshot(player.getPosition().x - 10, player.getPosition().y - 40, false));
				this.player.stopShooting();
				break;
				
			case SHOOT_RIGHT:
				this.player.startShooting();
				this.gunshotManager.addGunshot(new Gunshot(player.getPosition().x + 30, player.getPosition().y - 40, true));
				this.player.stopShooting();
				break;

			default:
				map.enterNewMessage(message);
				break;
		}
		
		collisionChecker.checkCollisions();
	}
	
	public Coordinates2D getPlayerPosition()
	{
		return player.getPosition();
	}
	
	public void enterNewConsoleCommand(Message message)
	{
		switch ((ConsoleCommand) message.command)
		{
			case SET_PLAYER_POSITION:
				player.setXY(message.position.x, message.position.y);
				break;
				
			case INVENTORY_ADD_BLOCKS:
				Inventory.getInstance().addItems(message.blockType, message.value);
				break;
				
			case SET_TIME:
				switch (message.value)
				{
					case 8:
						this.lastHour = Hours.T_08.ordinal();
						this.lastHourUpdate = System.currentTimeMillis();
						break;
						
					case 12:
						this.lastHour = Hours.T_12.ordinal();
						this.lastHourUpdate = System.currentTimeMillis();
						break;
						
					case 16:
						this.lastHour = Hours.T_16.ordinal();
						this.lastHourUpdate = System.currentTimeMillis();
						break;
						
					case 20:
						this.lastHour = Hours.T_20.ordinal();
						this.lastHourUpdate = System.currentTimeMillis();
						break;
						
					case 24:
						this.lastHour = Hours.T_24.ordinal();
						this.lastHourUpdate = System.currentTimeMillis();
						break;
						
					case 04:
						this.lastHour = Hours.T_04.ordinal();
						this.lastHourUpdate = System.currentTimeMillis();
						break;
					
					default:
						break;
				}
				
				break;
				
			case SET_DAY_LENGTH:
				this.DAY_LENGTH = message.value;
				break;

			case REMOVE_ALL_GUNSHOTS:
				this.gunshotManager.removeAll();
				break;
			
			case SET_FULL_HEALTH:
				this.player.setFullHealth();
				break;
				
			case SET_PLAYER_IMUNITY:
				this.player.setImunity(message.value * 1000);
				break;
				
			case REMOVE_PLAYER_IMUNITY:
				this.player.removeImunity();
				break;
				
			case SET_PLAYER_IMMORTALITY:
				this.player.setImmortality();
				break;
				
			case REMOVE_PLAYER_IMMORTALITY:
				this.player.removeImmortality();
				break;
				
			case KILL_ALL_ENEMIES:
				this.entityManager.killAllEnemies();
				break;
				
			default:
				this.map.enterNewConsoleCommand(message);
				break;
		}
	}

	/**
	 * Moves by objects based on time from last iteration.
	 * 
	 * @param deltaTime
	 */
	public void update(long deltaTime)
	{
		player.update(deltaTime);
		entityManager.update(deltaTime, player.getPosition().x, player.getPosition().y);
		gunshotManager.update(deltaTime);
		graphicEffectsManager.update(deltaTime);
	}

	public int getPlayersLives()
	{
		return player.getLives();
	}
	
	/*
	 * Defined times-of-days.
	 */
	private enum Hours
	{
		T_08,
		T_12,
		T_16,
		T_20,
		T_24,
		T_04,
	}
	
	// Position of camera and its default position
	public int cameraX = 4096 * Configuration.BLOCK_SIZE / 2;
	public int cameraY = 1024 * Configuration.BLOCK_SIZE / 2;
	
	// Reference to world's map
	private Map map = new Map();
	
	// Last time lighting update
	private int		lastHour		= 0;
	private long	lastHourUpdate	= -1;
	
	// Length of day (24h) in the game in milliseconds
	private int DAY_LENGTH = 10 * 60 * 1000;
	
	// Collision checker
	private CollisionChecker collisionChecker = new CollisionChecker(map.getBlocksReferences());

	// Instance of Player
	private Player player = new Player(2048 * 32, 512 * 32, collisionChecker);
	
	private EnemiesManager	entityManager 	= new EnemiesManager(collisionChecker);
	private GunshotManager	gunshotManager	= new GunshotManager(collisionChecker);
	
	private GraphicEffectsManager graphicEffectsManager = GraphicEffectsManager.getInstance();
}
